package com.breeze.components.core.util.json;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;

/**
 * Jackson序列化工具类 自定义配置方式: Jackson.getObjectMapper().configure(..).configure(..);
 *
 * @author breeze
 */
@Slf4j
public class Jackson {

  private static ObjectMapper MAPPER;

  private static final Set<JsonReadFeature> JSON_READ_FEATURES_ENABLED =
      CollUtil.newHashSet(
          // 允许在JSON中使用Java注释
          JsonReadFeature.ALLOW_JAVA_COMMENTS,
          // 允许 json 存在没用双引号括起来的 field
          JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES,
          // 允许 json 存在使用单引号括起来的 field
          JsonReadFeature.ALLOW_SINGLE_QUOTES,
          // 允许 json 存在没用引号括起来的 ascii 控制字符
          JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS,
          // 允许 json number 类型的数存在前导 0 (例: 0001)
          JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS,
          // 允许 json 存在 NaN, INF, -INF 作为 number 类型
          JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS,
          // 允许 只有Key没有Value的情况
          JsonReadFeature.ALLOW_MISSING_VALUES,
          // 允许数组json的结尾多逗号
          JsonReadFeature.ALLOW_TRAILING_COMMA);

  static {
    MAPPER = new ObjectMapper().findAndRegisterModules();
    // 当出现 Java 类中未知的属性时不报错，而是忽略此 JSON 字段
    MAPPER.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false);
  }

  static {
    try {
      // 初始化
      MAPPER = initMapper();
    } catch (Exception e) {
      log.error("jackson config error", e);
    }
  }

  public static ObjectMapper initMapper() {
    JsonMapper.Builder builder =
        JsonMapper.builder().enable(JSON_READ_FEATURES_ENABLED.toArray(new JsonReadFeature[0]));
    return initMapperConfig(builder.build());
  }

  public static ObjectMapper initMapperConfig(ObjectMapper objectMapper) {
    String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    objectMapper.setDateFormat(new SimpleDateFormat(dateTimeFormat));
    // 配置序列化级别
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    // 配置JSON缩进支持
    objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
    // 允许单个数值当做数组处理
    objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    // 禁止重复键, 抛出异常
    objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
    // 禁止使用int代表Enum的order()來反序列化Enum, 抛出异常
    objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
    // 有属性不能映射的时候不报错
    objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    // 对象为空时不抛异常
    objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    // 时间格式
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    // 允许未知字段
    objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
    // 序列化BigDecimal时之间输出原始数字还是科学计数, 默认false, 即是否以toPlainString()科学计数方式来输出
    objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
    // 识别Java8时间
    objectMapper.registerModule(new ParameterNamesModule());
    objectMapper.registerModule(new Jdk8Module());
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule
        .addSerializer(
            LocalDateTime.class,
            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)))
        .addDeserializer(
            LocalDateTime.class,
            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
    objectMapper.registerModule(javaTimeModule);
    //  if we use guava, we can add this line of code: objectMapper.registerModule(new
    // GuavaModule())
    return objectMapper;
  }

  public static ObjectMapper getObjectMapper() {
    return MAPPER;
  }

  /** JSON反序列化 */
  public static <V> V from(URL url, Class<V> type) {
    try {
      return MAPPER.readValue(url, type);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(URL url, TypeReference<V> type) {
    try {
      return MAPPER.readValue(url, type);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
    }
  }

  /** JSON反序列化（List） */
  public static <V> List<V> fromList(URL url, Class<V> type) {
    try {
      CollectionType collectionType =
          MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, type);
      return MAPPER.readValue(url, collectionType);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(InputStream inputStream, Class<V> type) {
    try {
      return MAPPER.readValue(inputStream, type);
    } catch (IOException e) {
      throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(InputStream inputStream, TypeReference<V> type) {
    try {
      return MAPPER.readValue(inputStream, type);
    } catch (IOException e) {
      throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
    }
  }

  /** JSON反序列化（List） */
  public static <V> List<V> fromList(InputStream inputStream, Class<V> type) {
    try {
      CollectionType collectionType =
          MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, type);
      return MAPPER.readValue(inputStream, collectionType);
    } catch (IOException e) {
      throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(File file, Class<V> type) {
    try {
      return MAPPER.readValue(file, type);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(File file, TypeReference<V> type) {
    try {
      return MAPPER.readValue(file, type);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
    }
  }

  /** JSON反序列化（List） */
  public static <V> List<V> fromList(File file, Class<V> type) {
    try {
      CollectionType collectionType =
          MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, type);
      return MAPPER.readValue(file, collectionType);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
    }
  }

  /** JSON反序列化 */
  public static <V> V from(String json, Class<V> type) {
    return from(json, (Type) type);
  }

  /** JSON反序列化 */
  public static <V> V from(String json, TypeReference<V> type) {
    return from(json, type.getType());
  }

  /** JSON反序列化 */
  public static <V> V from(String json, Type type) {
    if (StrUtil.isEmpty(json)) {
      return null;
    }
    try {
      JavaType javaType = MAPPER.getTypeFactory().constructType(type);
      return MAPPER.readValue(json, javaType);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
    }
  }

  /** JSON反序列化（List） */
  public static <V> List<V> fromList(String json, Class<V> type) {
    if (StrUtil.isEmpty(json)) {
      return Collections.emptyList();
    }
    try {
      CollectionType collectionType =
          MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, type);
      return MAPPER.readValue(json, collectionType);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
    }
  }

  /** JSON反序列化（Map） */
  public static Map<String, Object> fromMap(String json) {
    if (StrUtil.isEmpty(json)) {
      return Collections.emptyMap();
    }
    try {
      MapType mapType =
          MAPPER.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
      return MAPPER.readValue(json, mapType);
    } catch (IOException e) {
      throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json), e);
    }
  }

  /** 序列化为JSON */
  public static <V> String to(List<V> list) {
    try {
      return MAPPER.writeValueAsString(list);
    } catch (JsonProcessingException e) {
      throw new JacksonException(StrUtil.format("jackson to error, data: {}", list), e);
    }
  }

  /** 序列化为JSON */
  public static <V> String to(V v) {
    try {
      return MAPPER.writeValueAsString(v);
    } catch (JsonProcessingException e) {
      throw new JacksonException(StrUtil.format("jackson to error, data: {}", v), e);
    }
  }

  /** 序列化为JSON,指定序列化格式 */
  public static <V> String to(V v, PropertyNamingStrategy strategies) {
    try {
      return MAPPER.setPropertyNamingStrategy(strategies).writeValueAsString(v);
    } catch (JsonProcessingException e) {
      throw new JacksonException(StrUtil.format("jackson to error, data: {}", v), e);
    }
  }

  /** 序列化为JSON */
  public static <V> void toFile(String path, List<V> list) {
    try (Writer writer = new FileWriter(path, true)) {
      MAPPER.writer().writeValues(writer).writeAll(list);
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson to file error, path: {}, list: {}", path, list), e);
    }
  }

  /** 序列化为JSON */
  public static <V> void toFile(String path, V v) {
    try (Writer writer = new FileWriter(path, true)) {
      MAPPER.writer().writeValues(writer).write(v);
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson to file error, path: {}, data: {}", path, v), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return String，默认为 null
   */
  public static String getAsString(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return null;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return null;
      }
      return getAsString(jsonNode);
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get string error, json: {}, key: {}", json, key), e);
    }
  }

  private static String getAsString(JsonNode jsonNode) {
    return jsonNode.isTextual() ? jsonNode.textValue() : jsonNode.toString();
  }

  /**
   * 从json串中获取某个字段
   *
   * @return int，默认为 0
   */
  public static int getAsInt(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return 0;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return 0;
      }
      return jsonNode.isInt() ? jsonNode.intValue() : Integer.parseInt(getAsString(jsonNode));
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get int error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return long，默认为 0
   */
  public static long getAsLong(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return 0L;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return 0L;
      }
      return jsonNode.isLong() ? jsonNode.longValue() : Long.parseLong(getAsString(jsonNode));
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get long error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return double，默认为 0.0
   */
  public static double getAsDouble(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return 0.0;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return 0.0;
      }
      return jsonNode.isDouble()
          ? jsonNode.doubleValue()
          : Double.parseDouble(getAsString(jsonNode));
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get double error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return BigInteger，默认为 0.0
   */
  public static BigInteger getAsBigInteger(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return new BigInteger(String.valueOf(0.00));
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return new BigInteger(String.valueOf(0.00));
      }
      return jsonNode.isBigInteger()
          ? jsonNode.bigIntegerValue()
          : new BigInteger(getAsString(jsonNode));
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get big integer error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return BigDecimal，默认为 0.00
   */
  public static BigDecimal getAsBigDecimal(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return new BigDecimal("0.00");
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return new BigDecimal("0.00");
      }
      return jsonNode.isBigDecimal()
          ? jsonNode.decimalValue()
          : new BigDecimal(getAsString(jsonNode));
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get big decimal error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return boolean, 默认为false
   */
  public static boolean getAsBoolean(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return false;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return false;
      }
      if (jsonNode.isBoolean()) {
        return jsonNode.booleanValue();
      } else {
        if (jsonNode.isTextual()) {
          String textValue = jsonNode.textValue();
          return Convert.toBool(textValue);
        } else {
          // number
          return Convert.toBool(jsonNode.intValue());
        }
      }
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get boolean error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return byte[], 默认为 null
   */
  public static byte[] getAsBytes(String json, String key) {
    if (StrUtil.isEmpty(json)) {
      return new byte[0];
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return new byte[0];
      }
      return jsonNode.isBinary() ? jsonNode.binaryValue() : getAsString(jsonNode).getBytes();
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get byte error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return object, 默认为 null
   */
  public static <V> V getAsObject(String json, String key, Class<V> type) {
    if (StrUtil.isEmpty(json)) {
      return null;
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return null;
      }
      JavaType javaType = MAPPER.getTypeFactory().constructType(type);
      return from(getAsString(jsonNode), javaType);
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type),
          e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return list, 默认为 null
   */
  public static <V> List<V> getAsList(String json, String key, Class<V> type) {
    if (StrUtil.isEmpty(json)) {
      return Collections.emptyList();
    }
    try {
      JsonNode jsonNode = getAsJsonObject(json, key);
      if (null == jsonNode) {
        return Collections.emptyList();
      }
      CollectionType collectionType =
          MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, type);
      return from(getAsString(jsonNode), collectionType);
    } catch (Exception e) {
      throw new JacksonException(
          StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type),
          e);
    }
  }

  /**
   * 从json串中获取某个字段
   *
   * @return JsonNode, 默认为 null
   */
  public static JsonNode getAsJsonObject(String json, String key) {
    try {
      JsonNode node = MAPPER.readTree(json);
      if (null == node) {
        return null;
      }
      return node.get(key);
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson get object from json error, json: {}, key: {}", json, key), e);
    }
  }

  /**
   * 向json中添加属性
   *
   * @return json
   */
  public static <V> String add(String json, String key, V value) {
    try {
      JsonNode node = MAPPER.readTree(json);
      add(node, key, value);
      return node.toString();
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson add error, json: {}, key: {}, value: {}", json, key, value), e);
    }
  }

  /** 向json中添加属性 */
  private static <V> void add(JsonNode jsonNode, String key, V value) {
    switch (value) {
      case String s -> ((ObjectNode) jsonNode).put(key, s);
      case Short i -> ((ObjectNode) jsonNode).put(key, i);
      case Integer i -> ((ObjectNode) jsonNode).put(key, i);
      case Long l -> ((ObjectNode) jsonNode).put(key, l);
      case Float v -> ((ObjectNode) jsonNode).put(key, v);
      case Double v -> ((ObjectNode) jsonNode).put(key, v);
      case BigDecimal bigDecimal -> ((ObjectNode) jsonNode).put(key, bigDecimal);
      case BigInteger bigInteger -> ((ObjectNode) jsonNode).put(key, bigInteger);
      case Boolean b -> ((ObjectNode) jsonNode).put(key, b);
      case byte[] bytes -> ((ObjectNode) jsonNode).put(key, bytes);
      case null, default -> ((ObjectNode) jsonNode).put(key, to(value));
    }
  }

  /**
   * 除去json中的某个属性
   *
   * @return json
   */
  public static String remove(String json, String key) {
    try {
      JsonNode node = MAPPER.readTree(json);
      ((ObjectNode) node).remove(key);
      return node.toString();
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson remove error, json: {}, key: {}", json, key), e);
    }
  }

  /** 修改json中的属性 */
  public static <V> String update(String json, String key, V value) {
    try {
      JsonNode node = MAPPER.readTree(json);
      ((ObjectNode) node).remove(key);
      add(node, key, value);
      return node.toString();
    } catch (IOException e) {
      throw new JacksonException(
          StrUtil.format("jackson update error, json: {}, key: {}, value: {}", json, key, value),
          e);
    }
  }

  /**
   * 格式化Json(美化)
   *
   * @return json
   */
  public static String format(String json) {
    try {
      JsonNode node = MAPPER.readTree(json);
      return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(node);
    } catch (IOException e) {
      throw new JacksonException(StrUtil.format("jackson format json error, json: {}", json), e);
    }
  }

  /**
   * 判断字符串是否是json
   *
   * @return json
   */
  public static boolean isJson(String json) {
    try {
      MAPPER.readTree(json);
      return true;
    } catch (Exception e) {
      return false;
    }
  }
}
